package com.flame.schedule.service;

import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.flame.auth.core.context.AuthContextHolder;
import com.flame.core.mybatis.PageList;
import com.flame.core.utils.DateUtils;
import com.flame.schedule.constants.JobConstants;
import com.flame.schedule.controller.vo.JobInfoVo;
import com.flame.schedule.entity.JobInfo;
import com.flame.schedule.exception.JobInfoExceptionMessage;
import com.flame.schedule.exception.JobInfoRuntimeException;
import com.flame.schedule.executor.ExecutorService;
import com.flame.schedule.executor.FlameJob;
import com.flame.schedule.mapper.JobInfoMapper;
import com.flame.schedule.mapper.dto.JobInfoDto;
import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

/**
 * flame_schedule_job_info 任务信息表
 *
 * @author 吴欣童
 * @since 2022-12-03 19:56:44
 */
@Service
public class JobInfoService implements ApplicationRunner {

    private static final Logger log = LoggerFactory.getLogger(JobInfoService.class);

    @Autowired
    private Scheduler scheduler;

    @Autowired
    private ExecutorService executorService;

    @Autowired
    private JobInfoMapper jobInfoMapper;

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        JobInfoVo jobInfoVo = new JobInfoVo();
        jobInfoVo.setPageNum(0);
        jobInfoVo.setPageSize(0);
        List<JobInfoDto> jobInfoDtoList = jobInfoMapper.page(jobInfoVo);
        if (CollectionUtils.isEmpty(jobInfoDtoList)) {
            return;
        }
        for (JobInfoDto jobInfoDto : jobInfoDtoList) {
            try {
                JobKey jobKey = JobKey.jobKey(jobInfoDto.getJobName(), jobInfoDto.getJobGroup());
                JobDetail dbJobDetail = scheduler.getJobDetail(jobKey);
                if (dbJobDetail == null) {
                    JobInfo jobInfo = new JobInfo();
                    jobInfo.setJobName(jobInfoDto.getJobName());
                    jobInfo.setJobGroup(jobInfoDto.getJobGroup());
                    jobInfo.setBeanName(jobInfoDto.getBeanName());
                    jobInfo.setStartTime(jobInfoDto.getStartTime());
                    jobInfo.setEndTime(jobInfoDto.getEndTime());
                    jobInfo.setTriggerType(jobInfoDto.getTriggerType());
                    jobInfo.setIntervalSecond(jobInfoDto.getIntervalSecond());
                    jobInfo.setRepeatCount(jobInfoDto.getRepeatCount());
                    jobInfo.setCron(jobInfoDto.getCron());
                    jobInfo.setJobData(objectMapper.readValue(jobInfoDto.getJobData(), Map.class));

                    JobDetail jobDetail = getJobDetail(jobInfo);
                    Trigger trigger = getTrigger(jobInfo, jobDetail);
                    scheduler.scheduleJob(jobDetail, trigger);
                    log.info("init grout {} job {} success", jobInfo.getJobGroup(), jobInfo.getJobName());
                }
            } catch (JobInfoRuntimeException e) {
                e.printStackTrace();
            }
        }
    }

    public List<JobInfo> pageJobInfo(JobInfoVo jobInfoVo) {
        PageList<JobInfoDto> jobInfoDtoList = (PageList) jobInfoMapper.page(jobInfoVo);
        PageList<JobInfo> jobInfoList = new PageList<>();
        jobInfoList.setPageNum(jobInfoDtoList.getPageNum());
        jobInfoList.setPageSize(jobInfoDtoList.getPageSize());
        jobInfoList.setTotal(jobInfoDtoList.getTotal());
        jobInfoList.setPages(jobInfoDtoList.getPages());
        for (JobInfoDto jobInfoDto : jobInfoDtoList) {
            try {
                TriggerKey triggerKey = TriggerKey.triggerKey(jobInfoDto.getJobName(), jobInfoDto.getJobGroup());
                Trigger trigger = scheduler.getTrigger(triggerKey);
                Trigger.TriggerState triggerState = scheduler.getTriggerState(triggerKey);

                JobInfo jobInfo = new JobInfo();
                BeanUtils.copyProperties(jobInfoDto, jobInfo);
                jobInfo.setJobData(objectMapper.readValue(jobInfoDto.getJobData(), Map.class));
                if (trigger != null) {
                    jobInfo.setPreviousFireTime(trigger.getPreviousFireTime());
                    jobInfo.setNextFireTime(trigger.getNextFireTime());
                    jobInfo.setJobStatus(triggerState.toString());
                } else {
                    jobInfo.setJobStatus("COMPLETE");
                }
                jobInfoList.add(jobInfo);
            } catch (SchedulerException | JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return jobInfoList;
    }

    public void addJobInfo(JobInfo jobInfo) {
        JobInfoDto dbJobInfoDto = jobInfoMapper.selectById(jobInfo.getJobName(), jobInfo.getJobGroup());
        if (dbJobInfoDto != null) {
            throw new JobInfoRuntimeException(JobInfoExceptionMessage.JOBINFO_EXIST);
        }
        try {
            JobDetail jobDetail = getJobDetail(jobInfo);
            Trigger trigger = getTrigger(jobInfo, jobDetail);
            scheduler.scheduleJob(jobDetail, trigger);
            JobInfoDto jobInfoDto = new JobInfoDto();
            BeanUtils.copyProperties(jobInfo, jobInfoDto);
            jobInfoDto.setJobData(objectMapper.writeValueAsString(jobInfo.getJobData()));
            jobInfoMapper.insert(jobInfoDto);
        } catch (SchedulerException | JsonProcessingException e) {
            e.printStackTrace();
        }
    }

    private Trigger getTrigger(JobInfo jobInfo, JobDetail jobDetail) {
        JobDataMap jobDataMap = new JobDataMap();
        if (AuthContextHolder.getContext() != null) {
            jobInfo.setCreator(AuthContextHolder.getContext().getAccount());
        }
        jobDataMap.put("jobInfo", jobInfo);
        if (JobConstants.TRIGGER_TYPE_SIMPLE.equals(jobInfo.getTriggerType())) {
            SimpleScheduleBuilder simpleScheduleBuilder =
                SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(jobInfo.getIntervalSecond())
                    .withRepeatCount(jobInfo.getRepeatCount()).withMisfireHandlingInstructionNextWithExistingCount();
            return TriggerBuilder.newTrigger().withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup())
                .startAt(DateUtils.parse(jobInfo.getStartTime(), DateUtils.YYYY_MM_DD_HH_MM_SS))
                .endAt(DateUtils.parse(jobInfo.getEndTime(), DateUtils.YYYY_MM_DD_HH_MM_SS)).forJob(jobDetail)
                .usingJobData(jobDataMap).withSchedule(simpleScheduleBuilder).build();
        }
        if (JobConstants.TRIGGER_TYPE_CRON.equals(jobInfo.getTriggerType())) {
            CronScheduleBuilder cronScheduleBuilder =
                CronScheduleBuilder.cronSchedule(jobInfo.getCron()).inTimeZone(TimeZone.getTimeZone("GMT+8"))
                    .withMisfireHandlingInstructionDoNothing();
            return TriggerBuilder.newTrigger().withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup())
                .startAt(DateUtils.parse(jobInfo.getStartTime(), DateUtils.YYYY_MM_DD_HH_MM_SS))
                .endAt(DateUtils.parse(jobInfo.getEndTime(), DateUtils.YYYY_MM_DD_HH_MM_SS)).forJob(jobDetail)
                .usingJobData(jobDataMap).withSchedule(cronScheduleBuilder).build();
        }
        throw new JobInfoRuntimeException(JobInfoExceptionMessage.UNKNOWN_TRIGGER_TYPE);
    }

    private JobDetail getJobDetail(JobInfo jobInfo) {
        return JobBuilder.newJob(FlameJob.class).withIdentity(jobInfo.getJobName(), jobInfo.getJobGroup())
            .withDescription(jobInfo.getRemark()).storeDurably(true).requestRecovery(false).build();
    }

    public void updateJobInfo(JobInfo jobInfo) {
        try {
            TriggerKey oldTriggerKey = TriggerKey.triggerKey(jobInfo.getJobName(), jobInfo.getJobGroup());
            Trigger.TriggerState oldTriggerState = scheduler.getTriggerState(oldTriggerKey);

            JobDetail jobDetail = getJobDetail(jobInfo);
            Trigger trigger = getTrigger(jobInfo, jobDetail);

            scheduler.addJob(jobDetail, true);
            scheduler.rescheduleJob(oldTriggerKey, trigger);

            if (Trigger.TriggerState.PAUSED.equals(oldTriggerState)) {
                JobKey jobKey = JobKey.jobKey(jobInfo.getJobName(), jobInfo.getJobGroup());
                scheduler.pauseJob(jobKey);
            }

            JobInfoDto jobInfoDto = new JobInfoDto();
            BeanUtils.copyProperties(jobInfo, jobInfoDto);
            jobInfoDto.setJobData(objectMapper.writeValueAsString(jobInfo.getJobData()));
            jobInfoMapper.updateById(jobInfoDto);
        } catch (JsonProcessingException | SchedulerException e) {
            e.printStackTrace();
        }
    }

    public void deleteJobInfo(JobInfo jobInfo) {
        try {
            JobKey jobKey = JobKey.jobKey(jobInfo.getJobName(), jobInfo.getJobGroup());
            List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
            for (Trigger trigger : triggers) {
                TriggerKey triggerKey = trigger.getKey();
                scheduler.pauseTrigger(triggerKey);
                scheduler.unscheduleJob(triggerKey);
            }
            scheduler.deleteJob(jobKey);
            jobInfoMapper.deleteById(jobInfo.getJobName(), jobInfo.getJobGroup());
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    public void pausuJob(JobInfo jobInfo) {
        try {
            JobKey jobKey = JobKey.jobKey(jobInfo.getJobName(), jobInfo.getJobGroup());
            scheduler.pauseJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    public void resumeJob(JobInfo jobInfo) {
        try {
            JobKey jobKey = JobKey.jobKey(jobInfo.getJobName(), jobInfo.getJobGroup());
            scheduler.resumeJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    public void invoke(JobInfo jobInfo) {
        jobInfo.setTriggerType(JobConstants.TRIGGER_TYPE_MANUAL);
        if (AuthContextHolder.getContext() != null) {
            jobInfo.setCreator(AuthContextHolder.getContext().getAccount());
        }
        executorService.execute(jobInfo);
    }

    public List<String> jobGroupList() {
        return jobInfoMapper.listJobGroup();
    }
}
